-- The "modified attribute" takes the form of: modifier-name-button
-- The modifier is one of "shift-", "ctrl-", "alt-", and the button is a number from 1 through 5.
--
-- For example, you could set an action that is used for unmodified left click like this:
-- self:SetAttribute("action1", value);
-- You could set an action that is used for shift-right-click like this:
-- self:SetAttribute("shift-action2", value);
--
-- You can also use a wildcard in the place of the modifier or button to denote an attribute
-- that is used for any modifier or any button.
--
-- For example, you could set an action that is used for any left click like this:
-- self:SetAttribute("*action1", value);
-- You could set an action that is used for shift-click with any button like this:
-- self:SetAttribute("shift-action*", value);
--
ATTRIBUTE_NOOP = "";
-- You can exclude an action by explicitly setting its value to ATTRIBUTE_NOOP
--
-- For example, you could set an action that was used for all clicks except ctrl-left-click:
-- self:SetAttribute("*action*", value);
-- self:SetAttribute("shift-action1", ATTRIBUTE_NOOP);
--
-- Setting the attribute by itself is equivalent to *attribute*

-- Internal helper function for modifier parsing
local strsplit = strsplit;
local function ParseSplitModifierString(...)
    for i=1, select('#', ...) do
        local mod, rep = strsplit(':', (select(i, ...)));
        if ( IsModifiedClick(mod) ) then
            return rep or mod:lower();
        end
    end
end

-- Given a modifier string which consists of one or more modifier
-- clauses separated by commas, return the id of the first matching
-- clause. If no modifiers match then nil is returned.
--
-- A modifier clause is of the form MODIFIER or the form MODIFIER:prefix
--
-- e.g.
--
-- "SELFCAST:self,CTRL"
--
-- will return 'self' if the self cast modifier is held down, or 'ctrl' if
-- the control key is held down
function SecureButton_ParseModifierString(msg)
    return ParseSplitModifierString(strsplit(',', msg));
end

-- Get the current modifier prefix for a frame (optional), if the frame has
-- a modifiers attribute defined, then it will be parsed using
-- SecureButton_ParseModifiersString and used as the prefix.
--
-- If no frame is specified, or the frame has no modifiers property, then
-- the old style prefix functionality is used.
function SecureButton_GetModifierPrefix(frame)
    -- Handle optional frame modifiers attribute
    if ( frame ) then
        local modlist = frame:GetAttribute("modifiers");
        if ( modlist ) then
            local prefix = SecureButton_ParseModifierString(modlist);
            if ( prefix ) then
                return prefix .. "-";
            end
        end
    end

    local prefix = "";
    if ( IsShiftKeyDown() ) then
        prefix = "shift-"..prefix;
    end
    if ( IsControlKeyDown() ) then
        prefix = "ctrl-"..prefix;
    end
    if ( IsAltKeyDown() ) then
        prefix = "alt-"..prefix;
    end
        return prefix;
end

function SecureButton_GetButtonSuffix(button)
        local suffix = "";
    if ( button == "LeftButton" ) then
        suffix = "1";
    elseif ( button == "RightButton" ) then
        suffix = "2";
    elseif ( button == "MiddleButton" ) then
        suffix = "3";
    elseif ( button == "Button4" ) then
        suffix = "4";
    elseif ( button == "Button5" ) then
        return "5";
    elseif ( button and button ~= "" ) then
        suffix = "-" .. tostring(button);
    end
    return suffix;
end

function SecureButton_GetModifiedAttribute(frame, name, button, prefix, suffix)
        if ( not prefix ) then
                prefix = SecureButton_GetModifierPrefix(frame);
        end
        if ( not suffix ) then
                suffix = SecureButton_GetButtonSuffix(button);
        end
    local value = frame:GetAttribute(prefix, name, suffix);
    if ( not value and (frame:GetAttribute("useparent-"..name) or
                        frame:GetAttribute("useparent*")) ) then
        local parent = frame:GetParent();
        if ( parent ) then
            value = SecureButton_GetModifiedAttribute(parent, name, button, prefix, suffix);
        end
    end
    if ( value == ATTRIBUTE_NOOP ) then
        value = nil;
    end
    return value;
end
function SecureButton_GetAttribute(frame, name)
        return SecureButton_GetModifiedAttribute(frame, name, nil, "", "");
end

function SecureButton_GetModifiedUnit(self, button)
        local unit = SecureButton_GetModifiedAttribute(self, "unit", button);
        if ( unit ) then
                local unitsuffix = SecureButton_GetModifiedAttribute(self, "unitsuffix", button);
                if ( unitsuffix ) then
                        unit = unit .. unitsuffix;
                        -- map raid1pet to raidpet1
                        unit = gsub(unit, "^([^%d]+)([%d]+)[pP][eE][tT]", "%1pet%2");
                end
                return unit;
        end
        if ( SecureButton_GetModifiedAttribute(self, "checkselfcast", button) ) then
                if ( IsModifiedClick("SELFCAST") ) then
                        return "player";
                end
        end
        if ( SecureButton_GetModifiedAttribute(self, "checkfocuscast", button) ) then
                if ( IsModifiedClick("FOCUSCAST") ) then
                        return "focus";
                end
        end
end
function SecureButton_GetUnit(self)
        local unit = SecureButton_GetAttribute(self, "unit");
        if ( unit ) then
                local unitsuffix = SecureButton_GetAttribute(self, "unitsuffix");
                if ( unitsuffix ) then
                        unit = unit .. unitsuffix;
                        -- map raid1pet to raidpet1
                        unit = gsub(unit, "^([^%d]+)([%d]+)[pP][eE][tT]", "%1pet%2");
                end
                return unit;
        end
end

function SecureButton_GetEffectiveButton(self)
        -- We will be returning to implement this later
        return "LeftButton";
end

--
-- SecureActionButton
--
-- SecureActionButtons allow you to map different combinations of modifiers and buttons into
-- actions which are executed when the button is clicked.
--
-- For example, you could set up the button to respond to left clicks by targeting the focus:
-- self:SetAttribute("unit", "focus");
-- self:SetAttribute("type1", "target");
--
-- You could set up all other buttons to bring up a menu like this:
-- self:SetAttribute("type*", "menu");
-- self.showmenu = menufunc;
--
-- SecureActionButtons are also able to perform different actions depending on whether you can
-- attack the unit or assist the unit associated with the action button.  It does so by mapping
-- mouse buttons into "virtual buttons" based on the state of the unit. For example, you can use
-- the following to cast "Mind Blast" on a left click and "Shadow Word: Death" on a right click
-- if the unit can be attacked:
-- self:SetAttribute("harmbutton1", "nuke1");
-- self:SetAttribute("type-nuke1", "spell");
-- self:SetAttribute("spell-nuke1", "Mind Blast");
-- self:SetAttribute("harmbutton2", "nuke2");
-- self:SetAttribute("type-nuke2", "spell");
-- self:SetAttribute("spell-nuke2", "Shadow Word: Death");
--
-- In this example, we use the special attribute "harmbutton" which is used to map a virtual
-- button when the unit is attackable. We also have the attribute "helpbutton" which is used
-- when the unit can be assisted.
--
-- Although it may not be immediately obvious, we are able to use this new virtual button
-- to set up very complex click behaviors on buttons. For example, we can define a new "heal"
-- virtual button for all friendly left clicks, and then set the button to cast "Flash Heal"
-- on an unmodified left click and "Renew" on a ctrl left click:
-- self:SetAttribute("*helpbutton1", "heal");
-- self:SetAttribute("*type-heal", "spell");
-- self:SetAttribute("spell-heal", "Flash Heal");
-- self:SetAttribute("ctrl-spell-heal", "Renew");
--
-- This system is very powerful, and provides a good layer of abstraction for setting up
-- a button's click behaviors.

function SecureActionButton_OnClick(self, button)
        -- Lookup the unit, based on the modifiers and button
        local unit = SecureButton_GetModifiedUnit(self, button);

        -- Remap button suffixes based on the disposition of the unit (contributed by Iriel and Cladhaire)
        if ( unit ) then
                local origButton = button;
                if ( UnitCanAttack("player", unit) )then
                        button = SecureButton_GetModifiedAttribute(self, "harmbutton", button) or button;
                elseif ( UnitCanAssist("player", unit) )then
                        button = SecureButton_GetModifiedAttribute(self, "helpbutton", button) or button;
                end

                -- The unit may have changed based on button remapping
                if ( button ~= origButton ) then
                        unit = SecureButton_GetModifiedUnit(self, button);
                end
        end

        -- Don't do anything if our unit doesn't exist
        if ( unit and unit ~= "none" and not UnitExists(unit) ) then
                return;
        end

        -- Lookup the action type, based on the modifiers and button
        local type = SecureButton_GetModifiedAttribute(self, "type", button);

        -- Perform the requested action!
        if ( type == "actionbar" ) then
                local action = SecureButton_GetModifiedAttribute(self, "action", button);
                if ( action == "increment" ) then
                        ActionBar_PageUp();
                elseif ( action == "decrement" ) then
                        ActionBar_PageDown();
                elseif ( tonumber(action) ) then
                        ChangeActionBarPage(action);
                else
                        local a, b = strmatch(action, "^(%d+),%s*(%d+)$");
                        if ( GetActionBarPage() == tonumber(a) ) then
                                ChangeActionBarPage(b);
                        else
                                ChangeActionBarPage(a);
                        end
                end
        elseif ( type == "action" ) then
                local action = ActionButton_CalculateAction(self, button);
                if ( action ) then
                        -- Save macros in case the one for this action is being edited
                        securecall("MacroFrame_SaveMacro");

                        UseAction(action, unit, button);
                end
        elseif ( type == "pet" ) then
                local action = SecureButton_GetModifiedAttribute(self, "action", button);
                if ( action ) then
                        CastPetAction(action, unit);
                end
        elseif ( type == "spell" ) then
                local spell = SecureButton_GetModifiedAttribute(self, "spell", button);
                if ( spell ) then
                        CastSpellByName(spell, unit);
                end
        elseif ( type == "item" ) then
                local item = SecureButton_GetModifiedAttribute(self, "item", button);
                if ( not item ) then
                        -- Backwards compatibility code, deprecated but still handled for now.
                        local bag = SecureButton_GetModifiedAttribute(self, "bag", button);
                        local slot = SecureButton_GetModifiedAttribute(self, "slot", button);
                        if ( bag and slot ) then
                                item = bag.." "..slot;
                        else
                                item = slot;
                        end
                end
                if ( item ) then
                        local name, bag, slot = SecureCmdItemParse(item);
                        if ( IsEquippableItem(name) and not IsEquippedItem(name) ) then
                                EquipItemByName(name);
                        else
                                SecureCmdUseItem(name, bag, slot, unit);
                        end
                end
        elseif ( type == "macro" ) then
                local macro = SecureButton_GetModifiedAttribute(self, "macro", button);
                if ( macro ) then
                        -- Save macros in case the one for this action is being edited
                        securecall("MacroFrame_SaveMacro");

                        RunMacro(macro, button);
                else
                        local text = SecureButton_GetModifiedAttribute(self, "macrotext", button);
                        if ( text ) then
                                RunMacroText(text, button);
                        end
                end
        elseif ( type == "cancelaura" ) then
                local index = SecureButton_GetModifiedAttribute(self, "index", button);
                if ( index ) then
                        CancelUnitBuff(unit, index);
                else
                        local spell = SecureButton_GetModifiedAttribute(self, "spell", button);
                        local rank = SecureButton_GetModifiedAttribute(self, "rank", button);
                        CancelUnitBuff(unit, spell, rank);
                end
        elseif ( type == "stop" ) then
                if ( SpellIsTargeting() ) then
                        SpellStopTargeting();
                end
        elseif ( type == "target" ) then
                if ( unit ) then
                        if ( unit == "none" ) then
                                ClearTarget();
                        elseif ( SpellIsTargeting() ) then
                                SpellTargetUnit(unit);
                        elseif ( CursorHasItem() ) then
                                DropItemOnUnit(unit);
                        else
                                TargetUnit(unit);
                        end
                end
        elseif ( type == "focus" ) then
                FocusUnit(unit);
        elseif ( type == "assist" ) then
                AssistUnit(unit);
        elseif ( type == "maintank" or type == "mainassist" ) then
                local action = SecureButton_GetModifiedAttribute(self, "action", button);
                if ( not action or action == "set" ) then
                        SetPartyAssignment(type, unit);
                elseif ( action == "clear" ) then
                        ClearPartyAssignment(type, unit);
                elseif ( action == "toggle" ) then
                        if ( GetPartyAssignment(type, unit) ) then
                                ClearPartyAssignment(type, unit);
                        else
                                SetPartyAssignment(type, unit);
                        end
                end
        elseif ( type == "click" ) then
                local delegate = SecureButton_GetModifiedAttribute(self, "clickbutton", button);
                if ( delegate ) then
                        delegate:Click(button);
                end
        elseif ( type == "attribute" ) then
                local frame = SecureButton_GetModifiedAttribute(self, "attribute-frame", button);
                if ( not frame ) then
                        frame = self;
                end
                local name = SecureButton_GetModifiedAttribute(self, "attribute-name", button);
                local value = SecureButton_GetModifiedAttribute(self, "attribute-value", button);
                if ( name ) then
                        frame:SetAttribute(name, value);
                end
        elseif ( type ) then
                -- Custom action support
                local func = rawget(self, type);
                if ( func ) then
                        func(self, unit, button);
                end
        end

        -- Target predefined item, if we just cast a spell that targets an item
        if ( SpellCanTargetItem() ) then
                local bag = SecureButton_GetModifiedAttribute(self, "target-bag", button);
                local slot = SecureButton_GetModifiedAttribute(self, "target-slot", button);
                if ( slot ) then
                        if ( bag ) then
                                UseContainerItem(bag, slot);
                        else
                                UseInventoryItem(slot);
                        end
                else
                        local item = SecureButton_GetModifiedAttribute(self, "target-item", button);
                        if ( item ) then
                                SpellTargetItem(item);
                        end
                end
        end
end

function SecureUnitButton_OnLoad(self, unit, menufunc)
        self:SetAttribute("*type1", "target");
        self:SetAttribute("*type2", "menu");
        self:SetAttribute("unit", unit);
        self.menu = menufunc;
end

function SecureUnitButton_OnClick(self, button)
    local type = SecureButton_GetModifiedAttribute(self, "type", button);
    if ( type == "menu" ) then
        if ( SpellIsTargeting() ) then
            SpellStopTargeting();
            return;
        end
    end
    SecureActionButton_OnClick(self, button);
end

--
-- SecurePartyHeader and SecureRaidGroupHeader contributed with permission by: Esamynn, Cide, and Iriel
--

--[[
List of the various configuration attributes
======================================================
showRaid = [BOOLEAN] -- true if the header should be shown while in a raid
showParty = [BOOLEAN] -- true if the header should be shown while in a party and not in a raid
showPlayer = [BOOLEAN] -- true if the header should show the player when not in a raid
showSolo = [BOOLEAN] -- true if the header should be shown while not in a group (implies showPlayer)
nameList = [STRING] -- a comma separated list of player names (not used if 'groupFilter' is set)
groupFilter = [1-8, STRING] -- a comma seperated list of raid group numbers and/or uppercase class names and/or uppercase roles
strictFiltering = [BOOLEAN] - if true, then characters must match both a group and a class from the groupFilter list
point = [STRING] -- a valid XML anchoring point (Default: "TOP")
xOffset = [NUMBER] -- the x-Offset to use when anchoring the unit buttons (Default: 0)
yOffset = [NUMBER] -- the y-Offset to use when anchoring the unit buttons (Default: 0)
sortMethod = ["INDEX", "NAME"] -- defines how the group is sorted (Default: "INDEX")
sortDir = ["ASC", "DESC"] -- defines the sort order (Default: "ASC")
template = [STRING] -- the XML template to use for the unit buttons
templateType = [STRING] - specifies the frame type of the managed subframes (Default: "Button")
groupBy = [nil, "GROUP", "CLASS", "ROLE"] - specifies a "grouping" type to apply before regular sorting (Default: nil)
groupingOrder = [STRING] - specifies the order of the groupings (ie. "1,2,3,4,5,6,7,8")
maxColumns = [NUMBER] - maximum number of columns the header will create (Default: 1)
unitsPerColumn = [NUMBER or nil] - maximum units that will be displayed in a singe column, nil is infinate (Default: nil)
startingIndex = [NUMBER] - the index in the final sorted unit list at which to start displaying units (Default: 1)
columnSpacing = [NUMBER] - the ammount of space between the rows/columns (Default: 0)
columnAnchorPoint = [STRING] - the anchor point of each new column (ie. use LEFT for the columns to grow to the right)
--]]

function SecureGroupHeader_OnLoad(self)
        self:RegisterEvent("PARTY_MEMBERS_CHANGED");
        self:RegisterEvent("UNIT_NAME_UPDATE");
end

function SecureGroupHeader_OnEvent(self, event, ...)
        if ( (event == "PARTY_MEMBERS_CHANGED" or event == "UNIT_NAME_UPDATE") and self:IsVisible() ) then
                SecureGroupHeader_Update(self);
        end
end

function SecureGroupHeader_OnAttributeChanged(self, name, value)
        if ( self:IsVisible() ) then
                SecureGroupHeader_Update(self);
        end
end

-- relativePoint, xMultiplier, yMultiplier = getRelativePointAnchor( point )
-- Given a point return the opposite point and which axes the point
-- depends on.
local function getRelativePointAnchor( point )
        point = strupper(point);
        if (point == "TOP") then
                return "BOTTOM", 0, -1;
        elseif (point == "BOTTOM") then
                return "TOP", 0, 1;
        elseif (point == "LEFT") then
                return "RIGHT", 1, 0;
        elseif (point == "RIGHT") then
                return "LEFT", -1, 0;
        elseif (point == "TOPLEFT") then
                return "BOTTOMRIGHT", 1, -1;
        elseif (point == "TOPRIGHT") then
                return "BOTTOMLEFT", -1, -1;
        elseif (point == "BOTTOMLEFT") then
                return "TOPRIGHT", 1, 1;
        elseif (point == "BOTTOMRIGHT") then
                return "TOPLEFT", -1, 1;
        else
                return "CENTER", 0, 0;
        end
end

function ApplyUnitButtonConfiguration( ... )
        for i = 1, select("#", ...), 1 do
                local frame = select(i, ...);
                local anchor = frame:GetAttribute("initial-anchor");
                local width = tonumber(frame:GetAttribute("initial-width") or nil);
                local height = tonumber(frame:GetAttribute("initial-height")or nil);
                local scale = tonumber(frame:GetAttribute("initial-scale")or nil);
                local unitWatch = frame:GetAttribute("initial-unitWatch");
                if ( anchor ) then
                        local point, relPoint, xOffset, yOffset = strsplit(",", anchor);
                        relpoint = relpoint or point;
                        xOffset = tonumber(xOffset) or 0;
                        yOffset = tonumber(yOffset) or 0;
                        frame:SetPoint(point, frame:GetParent(), relPoint, xOffset, yOffset);
                end
                if ( width ) then
                        frame:SetWidth(width);
                end
                if ( height ) then
                        frame:SetHeight(height);
                end
                if ( scale ) then
                        frame:SetScale(scale);
                end
                if ( unitWatch ) then
                        if ( unitWatch == "state" ) then
                                RegisterUnitWatch(frame, true);
                        else
                                RegisterUnitWatch(frame);
                        end
                end

                -- call this function recursively for the current frame's children
                ApplyUnitButtonConfiguration(frame:GetChildren());
        end
end

local function ApplyConfig( header, newChild, defaultConfigFunction )
        local configFunction = header.initialConfigFunction or defaultConfigFunction;
        if ( type(configFunction) == "function" ) then
                configFunction(newChild);
                return true;
        end
end

function SetupUnitButtonConfiguration( header, newChild, defaultConfigFunction )
        newChild:AllowAttributeChanges();
        if ( securecall(ApplyConfig, header, newChild, defaultConfigFunction) ) then
                ApplyUnitButtonConfiguration(newChild);
        end
end

-- empties tbl and assigns the value true to each key passed as part of ...
local function fillTable( tbl, ... )
        for key in pairs(tbl) do
                tbl[key] = nil;
        end
        for i = 1, select("#", ...), 1 do
                local key = select(i, ...);
                key = tonumber(key) or key;
                tbl[key] = true;
        end
end

-- same as fillTable() except that each key is also stored in
-- the array portion of the table in order
local function doubleFillTable( tbl, ... )
        fillTable(tbl, ...);
        for i = 1, select("#", ...), 1 do
                tbl[i] = select(i, ...);
        end
end

--working tables
local tokenTable = {};
local sortingTable = {};
local groupingTable = {};
local tempTable = {};

-- creates child frames and finished configuring them
local function configureChildren(self)
        local point = self:GetAttribute("point") or "TOP"; --default anchor point of "TOP"
        local relativePoint, xOffsetMult, yOffsetMult = getRelativePointAnchor(point);
        local xMultiplier, yMultiplier =  abs(xOffsetMult), abs(yOffsetMult);
        local xOffset = self:GetAttribute("xOffset") or 0; --default of 0
        local yOffset = self:GetAttribute("yOffset") or 0; --default of 0
        local sortDir = self:GetAttribute("sortDir") or "ASC"; --sort ascending by default
        local columnSpacing = self:GetAttribute("columnSpacing") or 0;
        local startingIndex = self:GetAttribute("startingIndex") or 1;

        local unitCount = #sortingTable;
        local numDisplayed = unitCount - (startingIndex - 1);
        local unitsPerColumn = self:GetAttribute("unitsPerColumn");
        local numColumns;
        if ( unitsPerColumn and numDisplayed > unitsPerColumn ) then
                numColumns = min( ceil(numDisplayed / unitsPerColumn), (self:GetAttribute("maxColumns") or 1) );
        else
                unitsPerColumn = numDisplayed;
                numColumns = 1;
        end
        local loopStart = startingIndex;
        local loopFinish = min((startingIndex - 1) + unitsPerColumn * numColumns, unitCount)
        local step = 1;

        numDisplayed = loopFinish - (loopStart - 1);

        if ( sortDir == "DESC" ) then
                loopStart = unitCount - (startingIndex - 1);
                loopFinish = loopStart - (numDisplayed - 1);
                step = -1;
        end

        -- ensure there are enough buttons
        local needButtons = max(1, numDisplayed);
        if not ( self:GetAttribute("child"..needButtons) ) then
                local buttonTemplate = self:GetAttribute("template");
                local templateType = self:GetAttribute("templateType") or "Button";
                local name = self:GetName();
                if not ( name ) then
                        self:Hide();
                        return;
                end
                for i = 1, needButtons, 1 do
                        local childAttr = "child" .. i;
                        if not ( self:GetAttribute(childAttr) ) then
                                        local newButton = CreateFrame(templateType, name.."UnitButton"..i, self, buttonTemplate);
                                        SetupUnitButtonConfiguration(self, newButton);
                                        self:SetAttribute(childAttr, newButton);
                                        self:SetAttribute("frameref-"..childAttr, GetFrameHandle(newButton));
                        end
                end
        end

        local columnAnchorPoint, columnRelPoint, colxMulti, colyMulti;
        if ( numColumns > 1 ) then
                columnAnchorPoint = self:GetAttribute("columnAnchorPoint");
                columnRelPoint, colxMulti, colyMulti = getRelativePointAnchor(columnAnchorPoint);
        end

        local buttonNum = 0;
        local columnNum = 1;
        local columnUnitCount = 0;
        local currentAnchor = self;
        for i = loopStart, loopFinish, step do
                buttonNum = buttonNum + 1;
                columnUnitCount = columnUnitCount + 1;
                if ( columnUnitCount > unitsPerColumn ) then
                        columnUnitCount = 1;
                        columnNum = columnNum + 1;
                end

                local unitButton = self:GetAttribute("child"..buttonNum);
                unitButton:Hide();
                unitButton:ClearAllPoints();
                if ( buttonNum == 1 ) then
                        unitButton:SetPoint(point, currentAnchor, point, 0, 0);
                        if ( columnAnchorPoint ) then
                                unitButton:SetPoint(columnAnchorPoint, currentAnchor, columnAnchorPoint, 0, 0);
                        end

                elseif ( columnUnitCount == 1 ) then
                        local columnAnchor = self:GetAttribute("child"..(buttonNum - unitsPerColumn));
                        unitButton:SetPoint(columnAnchorPoint, columnAnchor, columnRelPoint, colxMulti * columnSpacing, colyMulti * columnSpacing);

                else
                        unitButton:SetPoint(point, currentAnchor, relativePoint, xMultiplier * xOffset, yMultiplier * yOffset);
                end
                unitButton:SetAttribute("unit", sortingTable[sortingTable[i]]);
                unitButton:Show();

                currentAnchor = unitButton;
        end
        repeat
                buttonNum = buttonNum + 1;
                local unitButton = self:GetAttribute("child"..buttonNum);
                if ( unitButton ) then
                        unitButton:Hide();
                        unitButton:SetAttribute("unit", nil);
                end
        until not ( unitButton )

        local unitButton = self:GetAttribute("child1");
        local unitButtonWidth = unitButton:GetWidth();
        local unitButtonHeight = unitButton:GetHeight();
        if ( numDisplayed > 0 ) then
                local width = xMultiplier * (unitsPerColumn - 1) * unitButtonWidth + ( (unitsPerColumn - 1) * (xOffset * xOffsetMult) ) + unitButtonWidth;
                local height = yMultiplier * (unitsPerColumn - 1) * unitButtonHeight + ( (unitsPerColumn - 1) * (yOffset * yOffsetMult) ) + unitButtonHeight;

                if ( numColumns > 1 ) then
                        width = width + ( (numColumns -1) * abs(colxMulti) * (width + columnSpacing) );
                        height = height + ( (numColumns -1) * abs(colyMulti) * (height + columnSpacing) );
                end

                self:SetWidth(width);
                self:SetHeight(height);
        else
                local minWidth = self:GetAttribute("minWidth") or (yMultiplier * unitButtonWidth);
                local minHeight = self:GetAttribute("minHeight") or (xMultiplier * unitButtonHeight);
                self:SetWidth( max(minWidth, 0.1) );
                self:SetHeight( max(minHeight, 0.1) );
        end
end

local function GetGroupHeaderType(self)
        local type, start, stop;

        local nRaid = GetNumRaidMembers();
        local nParty = GetNumPartyMembers();
        if ( nRaid > 0 and self:GetAttribute("showRaid") ) then
                type = "RAID";
        elseif ( (nRaid > 0 or nParty > 0) and self:GetAttribute("showParty") ) then
                type = "PARTY";
        elseif ( self:GetAttribute("showSolo") ) then
                type = "SOLO";
        end
        if ( type ) then
                if ( type == "RAID" ) then
                        start = 1;
                        stop = nRaid;
                else
                        if ( type == "SOLO" or self:GetAttribute("showPlayer") ) then
                                start = 0;
                        else
                                start = 1;
                        end
                        stop = nParty;
                end
        end
        return type, start, stop;
end

local function GetGroupRosterInfo(type, index)
        local _, unit, name, subgroup, className, role;
        if ( type == "RAID" ) then
                unit = "raid"..index;
                name, _, subgroup, _, _, className, _, _, _, role = GetRaidRosterInfo(index);
        else
                if ( index > 0 ) then
                        unit = "party"..index;
                else
                        unit = "player";
                end
                if ( UnitExists(unit) ) then
                        name = UnitName(unit);
                        _, className = UnitClass(unit);
                        if ( GetPartyAssignment("MAINTANK", unit) ) then
                                role = "MAINTANK";
                        elseif ( GetPartyAssignment("MAINASSIST", unit) ) then
                                role = "MAINASSIST";
                        end
                end
                subgroup = 1;
        end
        return unit, name, subgroup, className, role;
end

function SecureGroupHeader_Update(self)
        local nameList = self:GetAttribute("nameList");
        local groupFilter = self:GetAttribute("groupFilter");
        local sortMethod = self:GetAttribute("sortMethod");
        local groupBy = self:GetAttribute("groupBy");

        for key in pairs(sortingTable) do
                sortingTable[key] = nil;
        end

        -- See if this header should be shown
        local type, start, stop = GetGroupHeaderType(self);
        if ( not type ) then
                configureChildren(self);
                return;
        end

        if ( not groupFilter and not nameList ) then
                groupFilter = "1,2,3,4,5,6,7,8";
        end

        if ( groupFilter ) then
                -- filtering by a list of group numbers and/or classes
                fillTable(tokenTable, strsplit(",", groupFilter));
                local strictFiltering = self:GetAttribute("strictFiltering"); -- non-strict by default
                for i = start, stop, 1 do
                        local unit, name, subgroup, className, role = GetGroupRosterInfo(type, i);
                        if ( name and
                             ((not strictFiltering) and
                              (tokenTable[subgroup] or tokenTable[className] or (role and tokenTable[role])) -- non-strict filtering
                             ) or
                              (tokenTable[subgroup] and tokenTable[className]) -- strict filtering
                        ) then
                                tinsert(sortingTable, name);
                                sortingTable[name] = unit;
                                if ( groupBy == "GROUP" ) then
                                        groupingTable[name] = subgroup;

                                elseif ( groupBy == "CLASS" ) then
                                        groupingTable[name] = className;

                                elseif ( groupBy == "ROLE" ) then
                                        groupingTable[name] = role;

                                end
                        end
                end

                if ( groupBy ) then
                        local groupingOrder = self:GetAttribute("groupingOrder");
                        doubleFillTable(tokenTable, strsplit(",", groupingOrder));
                        for k in pairs(tempTable) do
                                tempTable[k] = nil;
                        end
                        for _, grouping in ipairs(tokenTable) do
                                grouping = tonumber(grouping) or grouping;
                                for k in ipairs(groupingTable) do
                                        groupingTable[k] = nil;
                                end
                                for index, name in ipairs(sortingTable) do
                                        if ( groupingTable[name] == grouping ) then
                                                tinsert(groupingTable, name);
                                                tempTable[name] = true;
                                        end
                                end
                                if ( sortMethod == "NAME" ) then -- sort by ID by default
                                        table.sort(groupingTable);
                                end
                                for _, name in ipairs(groupingTable) do
                                        tinsert(tempTable, name);
                                end
                        end
                        -- handle units whose group didn't appear in groupingOrder
                        for k in ipairs(groupingTable) do
                                groupingTable[k] = nil;
                        end
                        for index, name in ipairs(sortingTable) do
                                if not ( tempTable[name] ) then
                                        tinsert(groupingTable, name);
                                end
                        end
                        if ( sortMethod == "NAME" ) then -- sort by ID by default
                                table.sort(groupingTable);
                        end
                        for _, name in ipairs(groupingTable) do
                                tinsert(tempTable, name);
                        end

                        --copy the names back to sortingTable
                        for index, name in ipairs(tempTable) do
                                sortingTable[index] = name;
                        end

                elseif ( sortMethod == "NAME" ) then -- sort by ID by default
                        table.sort(sortingTable);

                end

        else
                -- filtering via a list of names
                doubleFillTable(sortingTable, strsplit(",", nameList));
                for i = start, stop, 1 do
                        local unit, name = GetGroupRosterInfo(type, i);
                        if ( sortingTable[name] ) then
                                sortingTable[name] = unit;
                        end
                end
                for i = #sortingTable, 1, -1 do
                        local name = sortingTable[i];
                        if ( sortingTable[name] == true ) then
                                tremove(sortingTable, i);
                        end
                end
                if ( sortMethod == "NAME" ) then
                        table.sort(sortingTable);
                end

        end

        configureChildren(self);
end

--[[
The Pet Header accepts all of the various configuration attributes of the
regular raid header, as well as the following
======================================================
useOwnerUnit = [BOOLEAN] - if true, then the owner's unit string is set on managed's frames "unit" attribute (instead of pet's)
filterOnPet = [BOOLEAN] - if true, then pet names are used when sorting/filtering the list
--]]

function SecureGroupPetHeader_OnLoad(self)
        self:RegisterEvent("PARTY_MEMBERS_CHANGED");
        self:RegisterEvent("UNIT_NAME_UPDATE");
        self:RegisterEvent("UNIT_PET");
end

function SecureGroupPetHeader_OnEvent(self, event, ...)
        if ( (event == "PARTY_MEMBERS_CHANGED" or event == "UNIT_NAME_UPDATE" or event == "UNIT_PET") and self:IsVisible() ) then
                SecureGroupPetHeader_Update(self);
        end
end

function SecureGroupPetHeader_OnAttributeChanged(self, name, value)
        if ( self:IsVisible() ) then
                SecureGroupPetHeader_Update(self);
        end
end

local function GetPetUnit(type, index)
        if ( type == "RAID" ) then
                return "raidpet"..index;
        elseif ( index > 0 ) then
                return "partypet"..index;
        else
                return "pet";
        end
end

function SecureGroupPetHeader_Update(self)
        local nameList = self:GetAttribute("nameList");
        local groupFilter = self:GetAttribute("groupFilter");
        local sortMethod = self:GetAttribute("sortMethod");
        local groupBy = self:GetAttribute("groupBy");
        local useOwnerUnit = self:GetAttribute("useOwnerUnit");
        local filterOnPet = self:GetAttribute("filterOnPet");

        for key in pairs(sortingTable) do
                sortingTable[key] = nil;
        end

        -- See if this header should be shown
        local type, start, stop = GetGroupHeaderType(self);
        if ( not type ) then
                configureChildren(self);
                return;
        end

        if ( not groupFilter and not nameList ) then
                groupFilter = "1,2,3,4,5,6,7,8";
        end

        if ( groupFilter ) then
                -- filtering by a list of group numbers and/or classes
                fillTable(tokenTable, strsplit(",", groupFilter));
                local strictFiltering = self:GetAttribute("strictFiltering"); -- non-strict by default
                for i = start, stop, 1 do
                        local unit, name, subgroup, className, role = GetGroupRosterInfo(type, i);
                        local petUnit = GetPetUnit(type, i);
                        if ( filterOnPet ) then
                                name = UnitName(petUnit);
                        end
                        if not ( useOwnerUnit ) then
                                unit = petUnit;
                        end
                        if ( UnitExists(petUnit) ) then
                                if ( name and
                                     ((not strictFiltering) and
                                      (tokenTable[subgroup] or tokenTable[className] or (role and tokenTable[role])) -- non-strict filtering
                                         ) or
                                          (tokenTable[subgroup] and tokenTable[className]) -- strict filtering
                                ) then
                                        tinsert(sortingTable, name);
                                        sortingTable[name] = unit;
                                        if ( groupBy == "GROUP" ) then
                                                groupingTable[name] = subgroup;

                                        elseif ( groupBy == "CLASS" ) then
                                                groupingTable[name] = className;

                                        elseif ( groupBy == "ROLE" ) then
                                                groupingTable[name] = role;

                                        end
                                end
                        end
                end

                if ( groupBy ) then
                        local groupingOrder = self:GetAttribute("groupingOrder");
                        doubleFillTable(tokenTable, strsplit(",", groupingOrder));
                        for k in pairs(tempTable) do
                                tempTable[k] = nil;
                        end
                        for _, grouping in ipairs(tokenTable) do
                                grouping = tonumber(grouping) or grouping;
                                for k in ipairs(groupingTable) do
                                        groupingTable[k] = nil;
                                end
                                for index, name in ipairs(sortingTable) do
                                        if ( groupingTable[name] == grouping ) then
                                                tinsert(groupingTable, name);
                                                tempTable[name] = true;
                                        end
                                end
                                if ( sortMethod == "NAME" ) then -- sort by ID by default
                                        table.sort(groupingTable);
                                end
                                for _, name in ipairs(groupingTable) do
                                        tinsert(tempTable, name);
                                end
                        end
                        -- handle units whose group didn't appear in groupingOrder
                        for k in ipairs(groupingTable) do
                                groupingTable[k] = nil;
                        end
                        for index, name in ipairs(sortingTable) do
                                if not ( tempTable[name] ) then
                                        tinsert(groupingTable, name);
                                end
                        end
                        if ( sortMethod == "NAME" ) then -- sort by ID by default
                                table.sort(groupingTable);
                        end
                        for _, name in ipairs(groupingTable) do
                                tinsert(tempTable, name);
                        end

                        --copy the names back to sortingTable
                        for index, name in ipairs(tempTable) do
                                sortingTable[index] = name;
                        end

                elseif ( sortMethod == "NAME" ) then -- sort by ID by default
                        table.sort(sortingTable);

                end

        else
                -- filtering via a list of names
                doubleFillTable(sortingTable, strsplit(",", nameList));
                for i = start, stop, 1 do
                        local unit, name = GetGroupRosterInfo(type, i);
                        local petUnit = GetPetUnit(type, i);
                        if ( filterOnPet ) then
                                name = UnitName(petUnit);
                        end
                        if not ( useOwnerUnit ) then
                                unit = petUnit;
                        end
                        if ( sortingTable[name] and UnitExists(petUnit) ) then
                                sortingTable[name] = unit;
                        end
                end
                for i = #sortingTable, 1, -1 do
                        local name = sortingTable[i];
                        if ( sortingTable[name] == true ) then
                                tremove(sortingTable, i);
                        end
                end
                if ( sortMethod == "NAME" ) then
                        table.sort(sortingTable);
                end

        end

        if ( useOwnerUnit and filterOnPet ) then
                -- sorting table currently contains pet unit strings and needs to contain owner unit strings
                for i, name in ipairs(sortingTable) do
                        local unit = sortingTable[name];
                        sortingTable[name] = gsub(unit, "raidpet([%d]+)", "raid%1");
                end

        elseif ( not useOwnerUnit and not filterOnPet ) then
                -- sorting table currently contains owner unit strings and needs to contain pet unit strings
                for i, name in ipairs(sortingTable) do
                        local unit = sortingTable[name];
                        sortingTable[name] = gsub(unit, "raid([%d]+)", "raidpet%1");
                end

        end

        configureChildren(self);
end
